﻿using System;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Reflection;

namespace WcfService1
{
    public static class ConvertDTOHelper
    {
        public static ObservableCollection<TRes> ConvertToDTO<TFrom, TRes>(this ObservableCollection<TFrom> sources)
        {
            var Results = new ObservableCollection<TRes>();
            Func<TRes> FunctNew = CreateInstanceOf<TRes>();

            foreach (TFrom source in sources)
            {
                TRes Res = FunctNew();
                foreach (PropertyInfo prop in source.GetType().GetProperties())
                {
                    foreach (PropertyInfo resProp in Res.GetType().GetProperties())
                    {
                        if (prop.Name.ToLower() == resProp.Name.ToLower())
                        {
                            Action<TRes, object> setter = resProp.GetValueSetter<TRes>();
                            Func<TFrom, object> getter = prop.GetValueGetter<TFrom>();
                            if (getter(source) == null) continue;
                            if (getter(source).GetType().ToString().ToLower().Contains("detail")) continue;
                            if (getter(source).GetType().ToString().ToLower().Contains("status"))
                            {
                                //setter(Res, getter(source).ConvertToDTO2(resProp.PropertyType));
                                continue;
                            }

                            setter(Res, getter(source));
                            break;
                        }
                    }
                }

                Results.Add(Res);
            }

            return Results;
        }


        private static void Map(Object Source, Object Destination)
        {
            foreach (PropertyInfo propertyInfo in Source.GetType().GetProperties())
            {
                foreach (PropertyInfo property in Destination.GetType().GetProperties())
                {
                    if (propertyInfo.Name.ToLower() == property.Name.ToLower())
                    {
                        if (property.PropertyType.ToString().ToLower().Contains("dto"))
                        {
                            object src = propertyInfo.GetValue(Source, null);
                            object dest = property.GetValue(Destination, null);
                            if (src == null || dest == null) continue;
                            Map(src, dest);
                        }
                        else
                        {
                            if (property.CanWrite)
                                property.SetValue(Destination, propertyInfo.GetValue(Source, null), null);
                        }

                        break;
                    }
                }
            }
        }

        public static TRes ConvertToDTO<TFrom, TRes>(this TFrom source)
        {
            Func<TRes> FunctNew = CreateInstanceOf<TRes>();
            TRes Res = FunctNew();
            if (source == null) return Res;
            foreach (PropertyInfo prop in typeof (TFrom).GetProperties())
            {
                foreach (PropertyInfo resProp in Res.GetType().GetProperties())
                {
                    if (prop.Name.ToLower() == resProp.Name.ToLower())
                    {
                        Action<TRes, object> setter = resProp.GetValueSetter<TRes>();
                        Func<TFrom, object> getter = prop.GetValueGetter<TFrom>();

                        if (getter(source) == null) continue;
                        if (getter(source).GetType().ToString().ToLower().Contains("core") ||
                            getter(source).GetType().ToString().ToLower().Contains("dto"))
                        {
                            if (getter(source).GetType().ToString() != "WcfService1.DTO.Status")
                                setter(Res, getter(source).ConvertToDTO2(resProp.PropertyType));
                        }

                        else
                            setter(Res, getter(source));

                        break;
                    }
                }
            }
            return Res;
        }

        public static Object ConvertToDTO2(this Object source, Type DestType)
        {
            if (source.GetType().Name.Contains("Bag") || source.GetType().Name.Contains("Observable")) return null;

            Func<object> FunctNew = CreateInstanceOf(DestType);
            object Res = FunctNew();
            if (source == null) return Res;
            foreach (PropertyInfo prop in source.GetType().GetProperties())
            {
                foreach (PropertyInfo resProp in Res.GetType().GetProperties())
                {
                    if (prop.Name.ToLower() == resProp.Name.ToLower())
                    {
                        if (resProp.PropertyType.ToString().ToLower().Contains("core") ||
                            resProp.PropertyType.ToString().ToLower().Contains("dto"))
                        {
                            Object src = null;
                            try
                            {
                                src = prop.GetValue(source, null);
                            }
                            catch (Exception ex)
                            {
                            }

                            if (src == null) continue;
                            if (resProp.PropertyType.ToString() != "WcfService1.DTO.Status")
                                resProp.SetValue(Res, src.ConvertToDTO2(resProp.PropertyType), null);
                        }
                        else
                        {
                            if (resProp.CanWrite)
                                resProp.SetValue(Res, prop.GetValue(source, null), null);
                        }

                        break;
                    }
                }
            }
            return Res;
        }


        private static Func<T> CreateInstanceOf<T>()
        {
            NewExpression NewExp = Expression.New(typeof (T));
            Expression<Func<T>> Lamda = Expression.Lambda<Func<T>>(NewExp);
            return Lamda.Compile();
        }

        private static Func<Object> CreateInstanceOf(Type type)
        {
            NewExpression NewExp = Expression.New(type);
            Expression<Func<object>> Lamda = Expression.Lambda<Func<Object>>(NewExp);
            return Lamda.Compile();
        }

        private static Func<T, object> GetValueGetter<T>(this PropertyInfo propertyInfo)
        {
            if (typeof (T) != propertyInfo.DeclaringType)
            {
                throw new ArgumentException();
            }


            ParameterExpression instance = Expression.Parameter(propertyInfo.DeclaringType, "i");

            MemberExpression property = Expression.Property(instance, propertyInfo);

            UnaryExpression convert = Expression.TypeAs(property, typeof (object));

            return (Func<T, object>) Expression.Lambda(convert, instance).Compile();
        }


        private static Action<T, object> GetValueSetter<T>(this PropertyInfo propertyInfo)
        {
            if (typeof (T) != propertyInfo.DeclaringType)
            {
                throw new ArgumentException();
            }


            ParameterExpression instance = Expression.Parameter(propertyInfo.DeclaringType, "i");

            ParameterExpression argument = Expression.Parameter(typeof (object), "a");

            MethodCallExpression setterCall = Expression.Call(
                instance,
                propertyInfo.GetSetMethod(),
                Expression.Convert(argument, propertyInfo.PropertyType));

            return (Action<T, object>) Expression.Lambda(setterCall, instance, argument)
                                           .Compile();
        }


        private static Action<object> GetValueSetter2(this PropertyInfo propertyInfo)
        {
            ParameterExpression instance = Expression.Parameter(propertyInfo.DeclaringType, "i");

            ParameterExpression argument = Expression.Parameter(typeof (object), "a");

            MethodCallExpression setterCall = Expression.Call(
                instance,
                propertyInfo.GetSetMethod(),
                Expression.Convert(argument, propertyInfo.PropertyType));

            return (Action<object>) Expression.Lambda(setterCall, instance, argument)
                                        .Compile();
        }


        private static Func<object> GetValueGetter2(this PropertyInfo propertyInfo)
        {
            ParameterExpression instance = Expression.Parameter(propertyInfo.DeclaringType, "i");

            MemberExpression property = Expression.Property(instance, propertyInfo);

            UnaryExpression convert = Expression.TypeAs(property, typeof (object));

            return (Func<object>) Expression.Lambda(convert, instance).Compile();
        }
    }
}